<?php
namespace Lavender;

class Filter
{
    const T_RAW = 0x0;
    const T_STRING = 0x1;
    const T_INT = 0x2;
    const T_FLOAT = 0x4;
    const T_BOOL = 0x8;
    const T_MAP = 0x10;


    const T_STRIP_ER = 0x20; //strip \r
    const T_STRIP_NL = 0x40; //strip \n and \r
    const T_STRIP_TAGS = 0x80; //strip html tags

    const T_EMAIL = 0x1000;
    const T_URL = 0x2000;
    const T_PHONE = 0x4000;
    const T_MOBILE = 0x8000;
    const T_IP = 0x10000;
    const T_OBJECT = 0x20000;

    /**
     * filter from souce data by types
     *
     * @param $source source data list
     * @param $definitions filter type list
     * @param $prefix output keys prefix
     *
     * @return array
     */
    public static function filter_map(array $source, array $definitions, $prefix = '')
    {
        $output = array();
        foreach ($definitions as $_k => $type) {
            if (!isset($source[$_k])) {
                continue;
            }
            $output[$prefix . $_k] = self::filter($source[$_k], $type,$_k);
        }
        return $output;
    }

    /**
     * filter from souce data by type
     *
     * @param $var source data
     * @param $rules filter type list
     *
     * @return array
     */
    public static function filter($var, $rules,$field,$default_required)
    {
        $min = null;
        $max = null;
        $required = $default_required;
        $attrs = null;
        if (is_int($rules)) {
            $type = $rules;
            $rules = null;
        } else {
            if (isset($rules['type']) && is_int($rules['type'])) {
                $type = $rules['type'];
                if (isset($rules['min'])) {
                    $min = intval($rules['min']);
                }
                if (isset($rules['max'])) {
                    $max = intval($rules['max']);
                }
                if (isset($rules['required'])) {
                    $required = $rules['required'];
                }
                if (isset($rules['attrs'])) {
                    $attrs = $rules['attrs'];
                }
            } else {
                throw new Exception("rules format error ! ".json_encode($rules,true));
            }
        }

        if ($type === self::T_RAW) {
            return $var;
        }

        //map
        if ($type & self::T_MAP) {
            if (is_array($var)) {
                $len = count($var);
                if (!is_null($min)) {
                    if ($len < $min) {
                        throw new Exception("field '{$field}' count size is too small ! ");
                    }
                }
                if (!is_null($max)) {
                    if ($len > $max) {
                        throw new Exception("field '{$field}' count size is too large !");
                    }
                }
                $tmp_type = $type ^ self::T_MAP;
                if ($tmp_type) {
                    $itemRules = ['type' => $tmp_type];
                    if (!is_null($attrs)) {
                        $itemRules['attrs'] = $attrs;
                    }
                    $res = [];
                    foreach ($var as $key => $value) {
                        $itemRet = self::filter($value, $itemRules,$key,$required);
                        if ($itemRet === false) {
                            throw new Exception("field '{$field}' is invalid !");
                        }
                        $res[$key] = $itemRet;
                    }
                    return $res;
                }
                else{
                    return $var;
                }
            } else if (!empty($var)) {
                throw new Exception("field '{$field}' can not be empty !");
            }
            return array();
        }

        if ($type & self::T_OBJECT) {
            if (is_array($var)) {
                if (!is_null($attrs)) {
                    $result = [];
                    foreach ($attrs as $attr => $rule) {
                        $req = isset($rule['required']) ? $rule['required'] : $required;
                        if(isset($var[$attr])){
                            $val = self::filter($var[$attr], $rule,$attr,$req);
                            $result[$attr] = $val;
                        }
                        else if($req){
                            throw new Exception("field '{$attr}' is required !");
                        }
                    }
                    return $result;
                }
            } else if (empty($var) && $required) {
                throw new Exception("field '{$field}' is required !");
            }
            else{
                throw new Exception("field '{$field}' is not a object !");
            }
        }

        //int
        if ($type & self::T_INT) {
            if (!is_numeric($var)) {
                throw new Exception("field '{$field}' is not a number !");
            }
            $var = intval($var);
            if (!is_null($min)) {
                if ($var < $min) {
                    throw new Exception("number '{$field}' is too small !");
                }
            }
            if (!is_null($max)) {
                if ($var > $max) {
                    throw new Exception("number '{$field}' is too large !");
                }
            }
            return $var;
        }

        //float
        if ($type & self::T_FLOAT) {
            if (!is_numeric($var)) {
                throw new Exception("number '{$field}' is not a number !");
            }
            $var = doubleval($var);
            if (!is_null($min)) {
                if ($var < $min) {
                    throw new Exception("number '{$field}' is too small !");
                }
            }
            if (!is_null($max)) {
                if ($var > $max) {
                    throw new Exception("number '{$field}' is too large !");
                }
            }
            return $var;
        }

        //boolean
        if ($type & self::T_BOOL) {
            $var = empty($var) ? 0 : 1;
            return $var;
        }

        //string above
        if ($type & self::T_STRING ||
            $type & self::T_IP ||
            $type & self::T_EMAIL ||
            $type & self::T_URL ||
            $type & self::T_PHONE ||
            $type & self::T_MOBILE ||
            $type & self::T_STRIP_ER ||
            $type & self::T_STRIP_NL ||
            $type & self::T_STRIP_TAGS
        ) {
            if (!is_string($var)) {
                throw new Exception("'{$field}' is not string !");
            }
            $len = strlen($var);
            if (!is_null($min)) {
                if ($len < $min) {
                    throw new Exception("'{$field}' length is too short !");
                }
            }
            if (!is_null($max)) {
                if ($len > $max) {
                    throw new Exception("'{$field}' length is too long !");
                }
            }
            if ($type & self::T_EMAIL) {
                $var = filter_var($var, FILTER_VALIDATE_EMAIL);
                if ($var === false) {
                    throw new Exception("'{$field}' is not a email !");
                }
            }
            if ($type & self::T_URL) {
                $var = filter_var($var, FILTER_VALIDATE_URL);
                if ($var === false) {
                    throw new Exception("'{$field}' is not a regular url !");
                }
            }
            if ($type & self::T_IP) {
                $var = filter_var($var, FILTER_VALIDATE_IP);
                if ($var === false) {
                    throw new Exception("'{$field}' is not a ip address !");
                }
            }
            if ($type & self::T_PHONE) {
                if (!preg_match('/^(\+?[0-9]{2,3})?[0-9]{3,7}\-[0-9]{6,8}(\-[0-9]{2,6})?$/', $var)) {
                    throw new Exception("'{$field}' is not a phone number !");
                }
            }
            if ($type & self::T_MOBILE) {
                if (!preg_match('/^(\+?[0-9]{2,3})?[0-9]{6,11}$/', $var)) {
                    throw new Exception("'{$field}' is not a mobile number !");
                }
            }
            //strip \r
            if ($type & self::T_STRIP_ER) {
                $var = str_replace("\r", '', $var);
            }

            //strip \n & \r
            if ($type & self::T_STRIP_NL) {
                $var = str_replace("\r", '', $var);
                $var = str_replace("\n", '', $var);
            }

            //strip html tags
            if ($type & self::T_STRIP_TAGS) {
                $var = strip_tags($var);
            }
            return $var;
        }
        throw new Exception("`{$field}` format error!");
    }
}



